/**
 * @addtogroup brd_xcu
 * brd_xcu
 * @{
 */
/**
 * @file brd_xcu.h
 * @brief 与主处理器通讯板级层
 * @note
 * 1. 关于流控的说明. \n
 *    主控器器发送通道buf_size的3/4后发送kXcuCh0MsgGetFreeSize消息获取空闲缓冲区大小,在有应答前,主控端不再发送该通道的数据 \n
 *
 * 2. 关于unit_size的说明. \n
 *    unit_size=0,传输以块的方式传输,参见后面"关于块方式传输" \n
 *    unit_size=1,流式数据传输方式,mcu->主控制器传输,最大支持kXcuMaxMsgSize字节的消息;反之,主控->mcu的话,不受限制(但不超过通道缓冲区大小) \n
 *      该方式适用于串口透传等.该方式支持流控,可以结合流控进行传输. \n
 *    对于unit_size > 1的情况,传输以unit_size的整数倍传输,该传输方式定义为颗粒度传输; \n
 *    颗粒度传输方式适用于传输CAN数据等固定大小的数据;该方式支持流控. \n
 *
 * 3. 关于块方式传输 \n
 *    块方式传输时,每次调用TXcuWriteChannelData为一个块,该对端要么收到整个块,要么丢失整个块,不会出现收到半个块的情况. \n
 *    该方式适用于传输消息的方式,这样消息肯定是完整的.但这种方式消息有最大限制,即发送的消息不能超过kXcuMaxMsgSize(主控端也是如此) \n
 *    该方式不支持流控
 *
 * @par 修改日志
 * <table>
 * <tr><th>Date <th>Version <th>Author <th>Description
 * <tr><td>2021-01-05 <td>1.0 <td>软件工程院 <td>创建初始版本
 * @par 示例
 * @code
 * Master(主机端, 如linux):================================================
   // 主进程中:
   Brd_Init();
   Brd_Open();
   ...
   // 线程A:
   Brd_THandle handle = Brd_XcuCreateDev("/dev/vcan1");
   Brd_XcuOpenDev(handle);
   ...
   Brd_XcuReadDev(handle, buf, size);
   ...
   Brd_XcuWriteDev(handle, data, size);
   ...
   Brd_XcuCloseDev(handle);
   ...
   Brd_XcuDestroyDev(handle);
   ...
   // 线程B:
   Brd_THandle handle = Brd_XcuCreateDev("/dev/vtest");
   Brd_XcuOpenDev(handle);
   ...
   Brd_XcuReadDev(handle, buf, size);
   ...
   Brd_XcuWriteDev(handle, data, size);
   ...
   Brd_XcuCloseDev(handle);
   ...
   Brd_XcuDestroyDev(handle);
   //  主进程中:
   Brd_Close();
   Brd_Done();
 * Master(主机端, 如cat1):================================================
   // 主线程:
   Brd_Init();
   Brd_Open();
   ...
   // xcu线程:
   Brd_XcuCreate(NULL, 0, NULL, 0);
   ...
   Brd_XcuSetDebug(1);  // 可选
   Brd_XcuSetDump(0);  // 可选, 按default打印
   Brd_XcuOpen(1);  // 主机端不用关心参数, 
   ...
   // 其他线程或xcu线程:
   Brd_THandle handle = NULL;
   while (!TSfThreadTerminated(&thread)) {
      if (Brd_XcuConnected()) {  // 等待xcu枚举设备
         if (handle == NULL) {
            handle = Brd_XcuCreateDev("/dev/vcan1");
            Brd_XcuOpenDev(handle);
         }

         if (handle != NULL) {
            Brd_XcuReadDev(handle, buf, size);
            ...
            Brd_XcuWriteDev(handle, data, size);
            ...
         }

         SfSleep(50);
      }
   }

   Brd_XcuCloseDev(handle);
   Brd_XcuDestroyDev(handle);
   ...
   // xcu线程:
   Brd_XcuClose();
   Brd_XcuDestroy();
   ...
   // 主线程:
   Brd_Close();
   Brd_Done();
 * Slave(从机端, 如mcu):========================================================
   static UInt8 g_xcu_rx_buf_[4 << 10];
   static UInt8 g_xcu_tx_buf_[12 << 1024];
   static const Brd_TXcuChannelInfo g_xcu_chn_info_[] = {
      {
         .owner = receiver,
         .reserved = 0,
         .unit_size = 1,
         .buf_size = 0,
         .OnDataRecv = OnRecv,
         .OnGetFreeSize = OnGetFreeSize,
         .name = "test",
      },
   };

   Brd_XcuCreate(g_xcu_tx_buf_, sizeof(g_xcu_tx_buf_), g_xcu_rx_buf_, sizeof(g_xcu_tx_buf_));
   ...
   Brd_XcuRegisterChannel(1, &g_xcu_chn_info_[0]);
   ...
   Brd_Open();
   Brd_XcuOpen(1);  // 参数告知master开机原因
   ...
   while (!TSfThreadTerminated(&thread)) {
      if (Brd_XcuConnected()) {
         Brd_XcuWriteChannelData(1, data, size, False);
      }

      SfSleep(50);
   }

   Brd_XcuClose();
   ...
   Brd_XcuDestroy();
 * Slave Bootloader(从机端, 如mcu):=============================================
   static UInt8 g_xcu_rx_buf_[4 << 10];
   static UInt8 g_xcu_tx_buf_[12 << 1024];
   static const Brd_TXcuChannelInfo g_xcu_chn_info_[] = {
      {
         .owner = receiver,
         .reserved = 0,
         .unit_size = 1,
         .buf_size = 0,
         .OnDataRecv = OnRecv,
         .OnGetFreeSize = OnGetFreeSize,
         .name = "test",
      },
   };

   Brd_XcuCreate(g_xcu_tx_buf_, sizeof(g_xcu_tx_buf_), g_xcu_rx_buf_, sizeof(g_xcu_tx_buf_));
   ...
   Brd_XcuRegisterChannel(1, &g_xcu_chn_info_[0]);
   ...
   Brd_Open();
   Brd_XcuOpen(1);  // 参数告知master开机原因
   ...
   while (1) {
      Brd_XcuNetRunOnce();
      Brd_XcuRunOnce();

      if (curr_tick - last_tick >= 10) {
         last_tick = curr_tick;

         if (Brd_XcuConnected()) {
            Brd_XcuWriteChannelData(1, data, size, False);
         }
      }
   }

   Brd_XcuClose();
   ...
   Brd_XcuDestroy();
 * Mid Slave(从机端, 如mcu):使用 mid_xcu.c 的情况================================
 * @endcode 
 * @copyright 杭州鸿泉物联网技术股份有限公司
 */

#ifndef _BRD_XCU_H_
#define _BRD_XCU_H_

#include <ofc_types.h>
#include "brd_common.h"

#ifdef __cplusplus
extern "C" {
#endif

enum Brd_TXcuErrorCodeTag {
    /// 来自drv_xcu_spi的错误,该错误的回调函数为xcu_net的线程
    kBrd_XcuEcSpiPkgError,              ///< Spi传输层的包错误(或者是DMA控制器的错误); 参数: 未使用
    kBrd_XcuEcSpiTimeOut,               ///< Spi传输层传输超时; 原因是上位机没有将完整的Spi包传输下来; 参数: 未使用

    /// 来自xcu_net的错误, 该错误的回调函数为xcu_net的线程
    kBrd_XcuEcNetRxDataDiscard = 16,    ///< 由于Xcu的接收缓冲区满导致收到的报文被丢弃; 参数: 丢弃的字节数
    kBrd_XcuEcNetImpossible = 17,       ///< 不可能产生的错误; 参数: 未使用

    /// 来自xcu的错误,该错误的回调函数未xcu线程
    kBrd_XcuEcInvalidData = 32,         ///< Xcu数据包之间存在无效数据,应该是前面的数据包包头丢失引起的; 参数: 无效数据的长度
    kBrd_XcuEcInvalidPkgSize,           ///< Xcu数据包的Size字段无效,应该是找到的数据包头错误; 参数: 未使用
    kBrd_XcuEcPkgSizeOutOfBuf,          ///< Xcu数据包的Size字段超过解码缓冲区大小,应该是找到的数据包头错误; 参数: 错误的包尺寸大小
    kBrd_XcuEcPkgCrcCheckError,         ///< Xcu数据包的校验错误,应该是找到的数据包头错误; 参数: 未使用
};
typedef enum Brd_TXcuErrorCodeTag Brd_TXcuErrorCode;

/**
 * 通道收到数据的回调函数类型
 * @param owner: 通道的拥有者
 * @param data: 数据缓冲区
 * @param size: 数据大小
 * @return 无返回值
 */
typedef void (*Brd_TOnXcuDataRecv)(void *owner, const void *data, Int32 size);

/**
 * 获取通道接收缓冲区空闲字节数的回调函数类型
 * @param owner: 通道的拥有者
 * @return 返回空闲字节数
 */
typedef UInt32 (*Brd_TOnXcuGetFreeSize)(void *owner);

/**
 * 通道信息
 */
struct Brd_TXcuChannelInfoTag
{
    void *owner;  ///< 通道的所有者
    Byte reserved;  ///< 保留
    Byte unit_size;  ///< 最小传输单元大小; 每次传输的字节数为unit_size的整数倍; 具体参见文件说明
    Word buf_size;  ///< 通道的缓冲区大小; 如果该通道无需下行流控,则设置为0; 注: 上行无流控
    char name[16];  ///< 通道名称,最多15个字母,以'\0'结尾;
    Brd_TOnXcuDataRecv OnDataRecv;  ///< 收到通道数据时的回调函数; 由Xcu线程调用
    Brd_TOnXcuGetFreeSize OnGetFreeSize;  ///< 获取该通道当前发送缓冲区空闲的字节数;如果该通道无需下行流控,则设置为NULL; 由xcu线程调用
};
typedef struct Brd_TXcuChannelInfoTag Brd_TXcuChannelInfo;

/**
 * 设置事件的函数
 * @param receiver: 事件接收者
 * @param event: 事件
 * @param flags: 设置的事件标记集合
 * @return 无返回值
 */
typedef void (*Brd_TXcuDoSetEvent)(void *receiver, void *event, UInt32 flags);

/**
 * 等待事件的函数,如果等到事件,则返回该事件,并清除事件
 * @param receiver: 事件接收者
 * @param event: 事件
 * @param timeout: 超时时间
 * @return 等待结果. -1: 超时; 0: 无事件; >0: 相应的事件
 */
typedef Int32 (*Brd_TXcuDoWaitEvent)(void *receiver, void *event, UInt32 timeout);

/**
 * 等待或者休眠指定时间
 * @param receiver: 事件接收者
 * @param ms: 时长,单位:ms
 * @return 无返回值
 */
typedef void (*Brd_TXcuDoSleep)(void *receiver, UInt32 ms);

/**
 * 锁定线程调度
 * @note 调用类似 SfKernelLock 的禁止系统调度的锁
 * @param receiver: 事件接收者
 * @param lock: 是否锁定; True: 锁定; False: 解锁 
 * @return 无返回值
 */
typedef void (*Brd_TXcuDoLock)(void *receiver, Bool lock);

/**
 * 当传输发生错误时,产生的回调函数类型
 * @note 本函数根据错误码的不同,会被xcu_net线程和xcu线程调用,开发时需要注意
 * @param receiver: 事件接收者, 即用户
 * @param code: 错误码
 * @param param: 错误的参数,参见 Brd_TXcuErrorCode 中每个错误码后面的说明
 * @return 无返回值
 */
typedef void(*Brd_TOnXcuError)(void *receiver, Brd_TXcuErrorCode code, Int32 param);

/**
 * 构造函数
 * @note 作为主控制器端时, 参数传入(NULL,0,NULL,0)即可
 * @param tx_buf: 发送缓冲区
 * @param tx_buf_size: 发送数据的缓冲区大小,要求为1024字节的倍数,建议为4K或以上
 * @param rx_buf: 接收缓冲区
 * @param rx_buf_size: 接收数据的缓冲区大小,要求为1024字节的倍数,建议为4K或以上 
 * @return 无返回值
 */
void Brd_XcuCreate(char *tx_buf, Int32 tx_buf_size, char *rx_buf, Int32 rx_buf_size);

/**
 * 析构函数
 * @note 主控制器端才能使用
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuDestroy(void);

/**
 * 扩展构造函数
 * @note 适用于用户自己管理 线程,事件, 锁,调度的情况
 *       主控制器端不支持该函数
 * @param tx_buf: 发送缓冲区
 * @param tx_buf_size: 发送数据的缓冲区大小,要求为1024字节的倍数,建议为4K或以上
 * @param rx_buf: 接收缓冲区
 * @param rx_buf_size: 接收数据的缓冲区大小,要求为1024字节的倍数,建议为4K或以上 
 * @param xcu_event: xcu使用的事件
 * @param xcu_net_event: xcu_net使用的事件
 * @return 无返回值
 */
void Brd_XcuCreateEx(char *tx_buf, Int32 tx_buf_size, char *rx_buf,
    Int32 rx_buf_size, void *xcu_event, void *xcu_net_event);

/**
 * 获取事件接收者
 * @note 主控制器端不支持该函数
 * @return 当返回值为非 NULL 时, 表示事件接收者 \n
 *         当返回值为 NULL 时, 表示未设置过事件接收者
 */
void *Brd_XcuEventReceiver(void);

/**
 * 设置事件接收者
 * @note 主控制器端不支持该函数
 * @param receiver: 事件接收者
 * @return 无返回值 
 */
void Brd_XcuSetEventReceiver(void *receiver);

/**
 * 设置 "设置事件" 的事件
 * @note 主控制器端不支持该函数
 * @param do_event: 事件方法
 * @return 无返回值 
 */
void Brd_XcuSetDoSetEvent(Brd_TXcuDoSetEvent do_event);

/**
 * 设置 "等待事件" 的事件
 * @note 主控制器端不支持该函数
 * @param do_event: 事件方法
 * @return 无返回值 
 */
void Brd_XcuSetDoWaitEvent(Brd_TXcuDoWaitEvent do_event);

/**
 * 设置休眠的事件
 * @note 主控制器端不支持该函数
 * @param do_event: 事件方法
 * @return 无返回值 
 */
void Brd_XcuSetDoSleep(Brd_TXcuDoSleep do_event);

/**
 * 设置锁的事件
 * @note 主控制器端不支持该函数
 * @param do_event: 事件方法
 * @return 无返回值 
 */
void Brd_XcuSetDoLock(Brd_TXcuDoLock do_event);

/**
 * 设置错误消息的事件
 * @param do_event: 事件方法
 * @return 无返回值 
 */
void Brd_XcuSetOnError(Brd_TOnXcuError do_event);

/**
 * 获取最大的通道数量
 * @return 返回最大的通道数量
 */
Int32 Brd_XcuMaxChannelCount(void);

/**
 * 注册一个通道.
 * @note
 *   -# 注册必须在 Brd_XcuOpen 前注册,否则后果不可预料
 *   -# channel_info信息由外部维护,在Brd_Xcu生命周期内不得释放,否则会异常崩溃
 *   -# 主控制器端不支持该函数
 * @param channel: 通道号, 从1开始(0通道为特殊通道,内部已经占用),且小于[Brd_XcuMaxChannelCount](@ref Brd_XcuMaxChannelCount)
 * @param channel_info: 注册的通道信息; 
 * @return 注册成功返回 True,否则返回 False,错误原因:
 *   -# 通道号超范围
 *   -# 通道已经被占用
 *   -# 名称不符合要求
 *   -# 已经调用Brd_XcuOpen
 *   -# 通道信息必填字段没有填写
 *   -# 块传输方式(unit_size=0)启动了流控(buf_size > 0)
 */
Bool Brd_XcuRegisterChannel(Int32 channel, const Brd_TXcuChannelInfo *channel_info);

/**
 * 打开
 * @note 主控制器端使用时, 不关心startup_reason
 * @param startup_reason: 告知主处理器启动的原因
 * @return 无返回值
 */
void Brd_XcuOpen(UInt8 startup_reason);

/**
 * 关闭
 * @return 无返回值
 */
void Brd_XcuClose(void);

/**
 * 执行 XCU 主体单元(内部仅执行一次)
 * @note
 *    -# 不带操作系统模式下, 一定要调用本函数运行, 建议周期 10 ms以下
 *    -# 带操作系统, 但用户自己管理线程时, 也一定要调用本函数运行, 建议周期 10 ms以下
 *    -# 主控制器端不支持该函数
 * @return 无返回值
 */
void Brd_XcuRunOnce(void);

/**
 * 执行 XCU 网络单元(内部仅执行一次)
 * @note
 *    -# 不带操作系统模式下, 一定要调用本函数运行, 建议周期 10 ms以下
 *    -# 带操作系统, 但用户自己管理线程时, 也一定要调用本函数运行, 建议周期 10 ms以下
 *    -# 主控制器端不支持该函数
 * @return 无返回值
 */
void Brd_XcuNetRunOnce(void);

/**
 * 获取最大的消息长度
 * @note 主控制器端不支持该函数
 * @return 返回最大的消息长度
 */
Int32 Brd_XcuMaxMsgSize(void);

/**
 * 写通道数据到内部缓冲区
 * @note 本函数线程安全,但不得在中断中调用
 *    -# 主控制器端不支持该函数
 * @param channel: 通道号, 从1开始(0通道为特殊通道,内部已经占用),且小于[Brd_XcuMaxChannelCount](@ref Brd_XcuMaxChannelCount)
 * @param data: 写入的数据缓冲区
 * @param size: 写入的数据大小; 注数据大小必须<=Brd_XcuMaxMsgSize()
 * @param import_data: 是否为重要数据; 非重要数据,如果写入后缓冲区空间不足将无法写入.重要数据没有这个限制
 * @return 写入成功返回 True,否则返回 False; 写入失败原因:
 *   -# 通道号不正确
 *   -# size超过规定大小
 *   -# 发送缓冲区空间不足
 */
Bool Brd_XcuWriteChannelData(Int32 channel, const void *data, Int32 size, Bool import_data);

/**
 * 判断当前xcu是否处于连接状态(即已经和主控制器握手成功).
 * @note 非活动状态调用 Brd_XcuWriteChannelData 会直接返回 False
 *    -# 主控制器端调用时, 表示与从控制器的握手状态
 * @return 当前xcu与主控制器已经连接并握手成功,返回 True,否则为 False
 */
Bool Brd_XcuConnected(void);

/**
 * 查看xcu的启动原因
 * @note 主机端用于查看从机端XCU的启动原因
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败, 参见[hcc_error_code_mid_xcu](@ref THccMidXcuErrorCodeTag)
 */
Int32 Brd_XcuGetStartupReason(void);

/**
 * 设置是否进入调试模式
 * @note 必须在Brd_XcuOpen之前调用,主控制器端才能使用
 * @param debug: 0: 不调试, 其他: 调试
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuSetDebug(Int32 debug);

/**
 * 设置收到数据包后的日志打印级别
 * @note 必须在Brd_XcuOpen之前调用,主控制器端才能使用
 * @param self: 对象实例指针
 * @param dump: 打印级别
 *            -# dump = 0: print error message while received a invalid package; default
 *            -# dump = 1: print datas of the package while received a invalid package
 *            -# dump = 2: print datas of the package from spi
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuSetDump(Int32 dump);

/**
 * 创建设备并构造
 * @note 主控制器端才能使用, 与从控制器握手成功(Brd_XcuConnected返回True)后调用
 *       线程不安全
 * @param name: 字符设备名称, 带'v', 如"/dev/vtest"
 * @return 当返回值非NULL时, 表示成功 \n
 *         当返回值NULL时, 表示失败
 */
Brd_THandle Brd_XcuCreateDev(const char *name);

/**
 * 析构设备
 * @note 主控制器端才能使用
 *       线程不安全
 * @param handle: 句柄
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuDestroyDev(Brd_THandle handle);

/**
 * 打开设备
 * @note 主控制器端才能使用
 *       线程不安全
 * @param handle: 句柄
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuOpenDev(Brd_THandle handle);

/**
 * 关闭设备
 * @note 主控制器端才能使用
 *       线程不安全
 * @param handle: 句柄
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuCloseDev(Brd_THandle handle);

/**
 * 从设备中读取数据
 * @note 主控制器端才能使用
 *       线程不安全
 * @param handle: 句柄
 * @param buf: 从设备读取的数据指针
 * @param size: 从设备读取的数据尺寸
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuReadDev(Brd_THandle handle, void *buf, Int32 size);

/**
 * 写数据到设备
 * @note 主控制器端才能使用
 *       线程不安全
 * @param handle: 句柄
 * @param data: 写入到设备的数据指针
 * @param size: 写入到设备的数据尺寸
 * @return 当返回值 >= 0 时, 表示成功 \n
 *         当返回值 < 0 时, 表示失败参见[brd_def](@ref Brd_TErrorCodeTag)
 */
Int32 Brd_XcuWriteDev(Brd_THandle handle, const void *data, Int32 size);

#ifdef __cplusplus
}  // extern "C"
#endif

#endif // _BRD_XCU_H_
/**
 * @} 
 */
